/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jpxx.workflow.util;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jpxx.workflow.DirectedGraph;
import org.jpxx.workflow.Node;
import org.jpxx.workflow.NodeType;
import org.jpxx.workflow.WorkFlowException;
import org.jpxx.workflow.WorkflowConstant;
import org.jpxx.workflow.rule.Rule;

/**
 * 
 * @author John Lee
 * 
 */
public class WorkFlowXMLParser {

	private static Logger logger = Logger.getLogger(WorkFlowXMLParser.class);

	public static DirectedGraph parse(String xml) throws WorkFlowException {
		InputStream is = new ByteArrayInputStream(xml.getBytes());
		return parse(is);
	}

	/**
	 * Load DirectedGraph form InputStream
	 * 
	 * @param is
	 * @return
	 * @throws WorkFlowException
	 */
	@SuppressWarnings("unchecked")
	public static DirectedGraph parse(InputStream is) throws WorkFlowException {

		String graphName = null;
		DirectedGraph rgraph = null;
		Map<String, Node> pm = new HashMap<String, Node>();
		Map<String, String> rootc = new HashMap<String, String>();

		SAXReader saxReader = new SAXReader();
		try {
			Document document = saxReader.read(is);
			Element rootElement = document.getRootElement();
			if (null == rootElement
					|| !WorkflowConstant.WORKFLOW_NODE_NAME.equals(rootElement
							.getName())) {
				throw new WorkFlowException("<"
						+ WorkflowConstant.WORKFLOW_NODE_NAME
						+ "> element not found in XML file.");
			}
			graphName = rootElement.attributeValue("name");
			if (StringUtils.isBlank(graphName)) {
				throw new WorkFlowException(WorkflowConstant.WORKFLOW_NODE_NAME
						+ " name attribute not found in XML file.");
			}
			rgraph = new DirectedGraph(graphName);

			List<Element> list = rootElement.element("nodes").elements("node");

			for (Element el : list) {

				String nodeName = el.attributeValue("id");
				if (nodeName == null) {
					throw new WorkFlowException("node id is not defined.");
				}
				String nodeType = el.attributeValue("type");
				if (nodeType == null) {
					throw new WorkFlowException("node type is not defined.");
				}
				Node gnode = new Node(nodeName, nodeType);

				String timeOut = el.attributeValue("timeoutMinutes");
				if (timeOut != null && !timeOut.equals("")) {
					Integer dTimeOut = new Integer(timeOut);
					gnode.setTimeoutMinutes(dTimeOut.intValue());
					String timeOutHandler = el.attributeValue("timeoutHandler");
					if (timeOutHandler != null) {
						gnode.setTimeoutHandler(timeOutHandler);
					}
				}
				logger.info("Putting: " + nodeName + " : "
						+ gnode.getNodeName() + " " + gnode.getNodeType());
				pm.put(nodeName, gnode);
				if (nodeType.equals(NodeType.CONTAINER.getType())) {
					String containee = el.attributeValue("containee");
					if (containee == null) {
						throw new WorkFlowException(
								"Containee not defined for container process");
					}
					gnode.setContainee(containee);
				}
			}

			list = rootElement.element("transitions").elements("transition");
			for (Element el : list) {
				String fromNodeName = el.attributeValue("from");
				String toNodeName = el.attributeValue("to");
				rootc.put(toNodeName, toNodeName);
				Node fromNode = (Node) pm.get(fromNodeName);
				Node toNode = (Node) pm.get(toNodeName);

				logger.info(fromNode.getNodeName() + " to "
						+ toNode.getNodeName());

				String rule = null;
				List<Element> ruleList = el.elements("rule");
				if (null != ruleList && ruleList.size() > 0) {
					Element e = (Element) ruleList.get(0);
					rule = e.getTextTrim();
					logger.info(rule);
				}
				fromNode.addDestination(toNode, Rule.parseRule(rule));

			}

		} catch (DocumentException e) {
			throw new WorkFlowException(e.getMessage());
		}

		try {
			String rootNodeId = findRootNodeId(pm, rootc);
			if (rootNodeId == null) {
				throw new WorkFlowException("No root node in graph");
			}
			Node rootNode = pm.get(rootNodeId);
			rgraph.setRootNode(rootNode);
			// rootNode.traverse();
		} catch (Exception e) {
			throw new WorkFlowException(e.getMessage());
		}
		return rgraph;
	}

	private static String findRootNodeId(Map<String, Node> pm,
			Map<String, String> rootc) throws Exception {

		String result = null;

		Iterator<String> itr = pm.keySet().iterator();
		while (itr.hasNext()) {
			String nid = (String) itr.next();
			if (rootc.get(nid) == null) {
				if (result == null) {
					result = nid;
				} else {
					throw new Exception("Graph has more than one root node");
				}
			}
		}
		return result;
	}
}